use cmsis_rtos2_rs::{
    osWaitForever, os_delay,
    static_mem::{mq_size, MessageQueue, MutexValue, Task, Thread},
};
use utils::{defer_ref, log, ufmt};

use super::{
    driver::{ctrl, sensor, APP_INFO},
    status, HeatErr,
};

#[derive(Clone, Copy, ufmt::derive::uDebug)]
pub enum State {
    IDLE,
    STOP,
    HEAT,
    TempFail,
    Timeout,
}

impl Into<u8> for State {
    fn into(self) -> u8 {
        match self {
            Self::IDLE => 0,
            Self::STOP => 1,
            Self::HEAT => 2,
            Self::TempFail => 3,
            Self::Timeout => 4,
        }
    }
}

enum Error {
    StateUpdate(State),
    Other(HeatErr),
    Timeout,
}

const MSG_COUNT: usize = 2;
struct WaterHeatTask {
    mq: MessageQueue<State, MSG_COUNT, { mq_size::<State>(MSG_COUNT) }>,
    state: MutexValue<State>,
}
static TASK: WaterHeatTask = WaterHeatTask {
    mq: MessageQueue::uninit(),
    state: MutexValue::uninit(State::IDLE),
};
static THREAD: Thread<1024> = Thread::uninit();
impl Task for WaterHeatTask {
    fn run(&self) {
        os_delay(500);

        let mut state = *self.state.lock();
        loop {
            log!("water heat:{:?}", state);
            let ret = match state {
                State::HEAT => self.do_heat(),
                State::IDLE => self.do_idle(),
                State::STOP => self.wait(osWaitForever),
                State::TempFail => self.do_heat_fail(),
                State::Timeout => self.wait(osWaitForever),
            };
            state = match ret {
                Ok(()) => state,
                Err(Error::StateUpdate(v)) => v,
                Err(Error::Other(HeatErr::Temp)) => State::TempFail,
                Err(Error::Other(_)) => state,
                Err(Error::Timeout) => State::Timeout,
            };
            *self.state.lock() = state;
        }
    }
}

impl WaterHeatTask {
    fn wait(&self, timeout: u32) -> Result<(), Error> {
        if let Ok(v) = self.mq.poll(timeout) {
            Err(Error::StateUpdate(v))
        } else {
            Ok(())
        }
    }

    fn do_idle(&self) -> Result<(), Error> {
        loop {
            if sensor::is_water_low() {
                return Err(Error::StateUpdate(State::HEAT));
            }
            self.wait(800)?;
        }
    }

    fn do_heat_fail(&self) -> Result<(), Error> {
        loop {
            if status::temp().is_ok() {
                return Err(Error::StateUpdate(State::IDLE));
            }
            self.wait(3000)?;
        }
    }

    fn do_heat(&self) -> Result<(), Error> {
        defer_ref!({
            ctrl::heat_water_close();
        });
        const DELAY: u32 = 2000;

        let mut time_count = 0;

        let timeout = APP_INFO.lock().water_heat_timeout as u32 * 1000;
        let water_temp = APP_INFO.lock().water_temp;

        loop {
            if !sensor::is_water_low() {
                return Err(Error::StateUpdate(State::IDLE));
            }

            let temp = status::temp().map_err(Error::Other)?;
            if temp.water < water_temp {

                if (time_count % DELAY) == 0 {
                    let diff = water_temp - temp.water;
                    match diff {
                        1..=15 => {
                            ctrl::heat_water_set_value(diff as u8);
                        }
                        _ => {
                            ctrl::heat_water_open();
                        }
                    };
                }
                time_count += 50;
                if time_count >= timeout {
                    return Err(Error::Timeout);
                }
            } else {
                ctrl::heat_water_close();
                time_count = 0;
            }
            self.wait(50)?;
        }
    }
}

pub unsafe fn start() {
    TASK.mq.init_once("water");
    TASK.state.init_once("water");
    THREAD.spawn(&TASK);
}

pub fn ctrl(state: State) {
    _ = TASK.mq.post(&state);
}

pub fn state() -> State {
    *TASK.state.lock()
}
